/*-------------------------------------------------------------------------
 *
 * regproc.c
 *	  Functions for the built-in types regproc, regclass, regtype, etc.
 *
 * These types are all binary-compatible with type Oid, and rely on Oid
 * for comparison and so forth.  Their only interesting behavior is in
 * special I/O conversion routines.
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/regproc.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>

#include "access/htup_details.h"
#include "catalog/namespace.h"
#include "catalog/pg_class.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_type.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "parser/parse_type.h"
#include "parser/scansup.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/regproc.h"
#include "utils/syscache.h"
#include "utils/varlena.h"

static void fc_parseNameAndArgTypes(const char *fc_string, bool fc_allowNone,
								 List **fc_names, int *fc_nargs, Oid *fc_argtypes);


/*****************************************************************************
 *	 用户 I/O 例程														 *
 *****************************************************************************/

/*
 * regprocin		- 将"proname"转换为proc OID
 *
 * 我们还接受数字OID，以与输出例程保持对称。
 *
 * '-'表示未知（OID 0）。在所有其他情况下，输入必须与现有的pg_proc条目匹配。
 */
Datum regprocin(PG_FUNCTION_ARGS)
{
	char	   *fc_pro_name_or_oid = PG_GETARG_CSTRING(0);
	RegProcedure fc_result = InvalidOid;
	List	   *fc_names;
	FuncCandidateList fc_clist;

	/* '-' ? */
	if (strcmp(fc_pro_name_or_oid, "-") == 0)
		PG_RETURN_OID(InvalidOid);

	/* 数字OID？ */
	if (fc_pro_name_or_oid[0] >= '0' &&
		fc_pro_name_or_oid[0] <= '9' &&
		strspn(fc_pro_name_or_oid, "0123456789") == strlen(fc_pro_name_or_oid))
	{
		fc_result = DatumGetObjectId(DirectFunctionCall1(oidin,
													  CStringGetDatum(fc_pro_name_or_oid)));
		PG_RETURN_OID(fc_result);
	}

	/* 否则它是一个名称，可能是模式限定的 */

	/*
	 * 我们在引导模式下永远不应该到这里，因为所有引用应该
	 * 已经通过 genbki.pl 解决。
	 */
	if (IsBootstrapProcessingMode())
		elog(ERROR, "regproc values must be OIDs in bootstrap mode");

	/*
	 * 正常情况：将名称解析为组件并查看它是否与当前搜索路径中的任何
	 * pg_proc 条目匹配。
	 */
	fc_names = stringToQualifiedNameList(fc_pro_name_or_oid);
	fc_clist = FuncnameGetCandidates(fc_names, -1, NIL, false, false, false, false);

	if (fc_clist == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function \"%s\" does not exist", fc_pro_name_or_oid)));
	else if (fc_clist->next != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
				 errmsg("more than one function named \"%s\"",
						fc_pro_name_or_oid)));

	fc_result = fc_clist->oid;

	PG_RETURN_OID(fc_result);
}

/*
 * to_regproc - 将 "proname" 转换为 proc OID
 *
 * 如果未找到该名称，我们返回 NULL。
 */
Datum to_regproc(PG_FUNCTION_ARGS)
{
	char	   *fc_pro_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
	List	   *fc_names;
	FuncCandidateList fc_clist;

	/*
	 * 将名称解析为组件，查看它是否与当前搜索路径中的任何 pg_proc
	 * 条目匹配。
	 */
	fc_names = stringToQualifiedNameList(fc_pro_name);
	fc_clist = FuncnameGetCandidates(fc_names, -1, NIL, false, false, false, true);

	if (fc_clist == NULL || fc_clist->next != NULL)
		PG_RETURN_NULL();

	PG_RETURN_OID(fc_clist->oid);
}

/*
 * regprocout - 将 proc OID 转换为 "pro_name"
 */
Datum regprocout(PG_FUNCTION_ARGS)
{
	RegProcedure fc_proid = PG_GETARG_OID(0);
	char	   *fc_result;
	HeapTuple	fc_proctup;

	if (fc_proid == InvalidOid)
	{
		fc_result = pstrdup("-");
		PG_RETURN_CSTRING(fc_result);
	}

	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_proid));

	if (HeapTupleIsValid(fc_proctup))
	{
		Form_pg_proc fc_procform = (Form_pg_proc) GETSTRUCT(fc_proctup);
		char	   *fc_proname = NameStr(fc_procform->proname);

		/*
		 * 在引导模式下，跳过花哨的命名空间处理，直接返回
		 * proc 名称。  (无论如何，此路径只需要用于调试输出。)
		 */
		if (IsBootstrapProcessingMode())
			fc_result = pstrdup(fc_proname);
		else
		{
			char	   *fc_nspname;
			FuncCandidateList fc_clist;

			/*
			 * 这个 proc 会被 regprocin 唯一找到吗？如果没有，
			 * 进行限定。
			 */
			fc_clist = FuncnameGetCandidates(list_make1(makeString(fc_proname)),
										  -1, NIL, false, false, false, false);
			if (fc_clist != NULL && fc_clist->next == NULL &&
				fc_clist->oid == fc_proid)
				fc_nspname = NULL;
			else
				fc_nspname = get_namespace_name(fc_procform->pronamespace);

			fc_result = quote_qualified_identifier(fc_nspname, fc_proname);
		}

		ReleaseSysCache(fc_proctup);
	}
	else
	{
		/* 如果 OID 与任何 pg_proc 条目不匹配，返回其数量 */
		fc_result = (char *) palloc(NAMEDATALEN);
		snprintf(fc_result, NAMEDATALEN, "%u", fc_proid);
	}

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		regprocrecv - 将外部二进制格式转换为 regproc
 */
Datum regprocrecv(PG_FUNCTION_ARGS)
{
	/* 完全与 oidrecv 相同，因此共享代码 */
	return oidrecv(fcinfo);
}

/*
 *		regprocsend - 将 regproc 转换为二进制格式
 */
Datum regprocsend(PG_FUNCTION_ARGS)
{
	/* 完全与 oidsend 相同，因此共享代码 */
	return oidsend(fcinfo);
}


/*
 * regprocedurein - 将 "proname(args)" 转换为 proc OID
 *
 * 我们也接受一个数字 OID，以便与输出例程对称。
 *
 * '-' 表示未知 (OID 0)。  在所有其他情况下，输入必须
 * 与现有 pg_proc 条目匹配。
 */
Datum regprocedurein(PG_FUNCTION_ARGS)
{
	char	   *fc_pro_name_or_oid = PG_GETARG_CSTRING(0);
	RegProcedure fc_result = InvalidOid;
	List	   *fc_names;
	int			fc_nargs;
	Oid			fc_argtypes[FUNC_MAX_ARGS];
	FuncCandidateList fc_clist;

	/* '-' ? */
	if (strcmp(fc_pro_name_or_oid, "-") == 0)
		PG_RETURN_OID(InvalidOid);

	/* 数字OID？ */
	if (fc_pro_name_or_oid[0] >= '0' &&
		fc_pro_name_or_oid[0] <= '9' &&
		strspn(fc_pro_name_or_oid, "0123456789") == strlen(fc_pro_name_or_oid))
	{
		fc_result = DatumGetObjectId(DirectFunctionCall1(oidin,
													  CStringGetDatum(fc_pro_name_or_oid)));
		PG_RETURN_OID(fc_result);
	}

	/* 此后的内容在引导模式下无法工作 */
	if (IsBootstrapProcessingMode())
		elog(ERROR, "regprocedure values must be OIDs in bootstrap mode");

	/*
	 * 否则它是一个名称和参数。解析名称和参数，查找
	 * 当前命名空间搜索列表中的潜在匹配项，并扫描以查看
	 * 哪一个与给定的参数类型完全匹配。  (不会有
	 * 超过一个匹配。)
	 */
	fc_parseNameAndArgTypes(fc_pro_name_or_oid, false, &fc_names, &fc_nargs, fc_argtypes);

	fc_clist = FuncnameGetCandidates(fc_names, fc_nargs, NIL, false, false,
								  false, false);

	for (; fc_clist; fc_clist = fc_clist->next)
	{
		if (memcmp(fc_clist->args, fc_argtypes, fc_nargs * sizeof(Oid)) == 0)
			break;
	}

	if (fc_clist == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function \"%s\" does not exist", fc_pro_name_or_oid)));

	fc_result = fc_clist->oid;

	PG_RETURN_OID(fc_result);
}

/*
 * to_regprocedure - 将 "proname(args)" 转换为 proc OID
 *
 * 如果未找到该名称，我们返回 NULL。
 */
Datum to_regprocedure(PG_FUNCTION_ARGS)
{
	char	   *fc_pro_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
	List	   *fc_names;
	int			fc_nargs;
	Oid			fc_argtypes[FUNC_MAX_ARGS];
	FuncCandidateList fc_clist;

	/*
	 * 解析名称和参数，查找当前命名空间搜索列表中的潜在匹配项，
	 * 并扫描以查看哪一个与给定的参数类型完全匹配。    (不会有
	 * 超过一个匹配。)
	 */
	fc_parseNameAndArgTypes(fc_pro_name, false, &fc_names, &fc_nargs, fc_argtypes);

	fc_clist = FuncnameGetCandidates(fc_names, fc_nargs, NIL, false, false, false, true);

	for (; fc_clist; fc_clist = fc_clist->next)
	{
		if (memcmp(fc_clist->args, fc_argtypes, fc_nargs * sizeof(Oid)) == 0)
			PG_RETURN_OID(fc_clist->oid);
	}

	PG_RETURN_NULL();
}

/*
 * format_procedure - 将 proc OID 转换为 "pro_name(args)"
 *
 * 这导出了 regprocedureout 的有用功能，以供
 * 其他后端模块使用。结果是一个 palloc'd 字符串。
 */
char * format_procedure(Oid fc_procedure_oid)
{
	return format_procedure_extended(fc_procedure_oid, 0);
}

char * format_procedure_qualified(Oid fc_procedure_oid)
{
	return format_procedure_extended(fc_procedure_oid, FORMAT_PROC_FORCE_QUALIFY);
}

/*
 * format_procedure_extended - 将程序 OID 转换为 "pro_name(args)"
 *
 * 这导出了 regprocedureout 的有用功能，以供
 * 其他后端模块使用。结果是一个 palloc'd 字符串，或者 NULL。
 *
 * 例程以生成 regprocedure 名称；请参阅上面的 format_procedure。
 *
 * 'flags' 中的以下位修改行为：
 * - FORMAT_PROC_INVALID_AS_NULL
 *			如果程序 OID 无效或未知，则返回 NULL 而不是
 *			数字 OID。
 * - FORMAT_PROC_FORCE_QUALIFY
 *			始终对程序名称进行模式限定，而不考虑 search_path
 */
char * format_procedure_extended(Oid fc_procedure_oid, bits16 fc_flags)
{
	char	   *fc_result;
	HeapTuple	fc_proctup;

	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_procedure_oid));

	if (HeapTupleIsValid(fc_proctup))
	{
		Form_pg_proc fc_procform = (Form_pg_proc) GETSTRUCT(fc_proctup);
		char	   *fc_proname = NameStr(fc_procform->proname);
		int			fc_nargs = fc_procform->pronargs;
		int			fc_i;
		char	   *fc_nspname;
		StringInfoData fc_buf;

		/* XXX 在引导模式下没有支持 */
		Assert(!IsBootstrapProcessingMode());

		initStringInfo(&fc_buf);

		/*
		 * 这个 proc 会被 regprocedurein 找到（给定正确的参数）吗？
		 * 如果没有，或者如果调用者要求，我们需要对其进行限定。
		 */
		if ((fc_flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
			FunctionIsVisible(fc_procedure_oid))
			fc_nspname = NULL;
		else
			fc_nspname = get_namespace_name(fc_procform->pronamespace);

		appendStringInfo(&fc_buf, "%s(",
						 quote_qualified_identifier(fc_nspname, fc_proname));
		for (fc_i = 0; fc_i < fc_nargs; fc_i++)
		{
			Oid			fc_thisargtype = fc_procform->proargtypes.values[fc_i];

			if (fc_i > 0)
				appendStringInfoChar(&fc_buf, ',');
			appendStringInfoString(&fc_buf,
								   (fc_flags & FORMAT_PROC_FORCE_QUALIFY) != 0 ?
								   format_type_be_qualified(fc_thisargtype) :
								   format_type_be(fc_thisargtype));
		}
		appendStringInfoChar(&fc_buf, ')');

		fc_result = fc_buf.data;

		ReleaseSysCache(fc_proctup);
	}
	else if ((fc_flags & FORMAT_PROC_INVALID_AS_NULL) != 0)
	{
		/* 如果对象未定义，返回 NULL，正如调用者所需 */
		fc_result = NULL;
	}
	else
	{
		/* 如果 OID 与任何 pg_proc 条目不匹配，返回其数量 */
		fc_result = (char *) palloc(NAMEDATALEN);
		snprintf(fc_result, NAMEDATALEN, "%u", fc_procedure_oid);
	}

	return fc_result;
}

/*
 * 输出给定 OID 的程序的 objname/objargs 表示。如果它不存在，将抛出错误。
 *
 * 这可以用于填充 get_object_address。
 */
void format_procedure_parts(Oid fc_procedure_oid, List **fc_objnames, List **fc_objargs,
					   bool fc_missing_ok)
{
	HeapTuple	fc_proctup;
	Form_pg_proc fc_procform;
	int			fc_nargs;
	int			fc_i;

	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_procedure_oid));

	if (!HeapTupleIsValid(fc_proctup))
	{
		if (!fc_missing_ok)
			elog(ERROR, "cache lookup failed for procedure with OID %u", fc_procedure_oid);
		return;
	}

	fc_procform = (Form_pg_proc) GETSTRUCT(fc_proctup);
	fc_nargs = fc_procform->pronargs;

	*fc_objnames = list_make2(get_namespace_name_or_temp(fc_procform->pronamespace),
						   pstrdup(NameStr(fc_procform->proname)));
	*fc_objargs = NIL;
	for (fc_i = 0; fc_i < fc_nargs; fc_i++)
	{
		Oid			fc_thisargtype = fc_procform->proargtypes.values[fc_i];

		*fc_objargs = lappend(*fc_objargs, format_type_be_qualified(fc_thisargtype));
	}

	ReleaseSysCache(fc_proctup);
}

/*
 * regprocedureout - 将 proc OID 转换为 "pro_name(args)"
 */
Datum regprocedureout(PG_FUNCTION_ARGS)
{
	RegProcedure fc_proid = PG_GETARG_OID(0);
	char	   *fc_result;

	if (fc_proid == InvalidOid)
		fc_result = pstrdup("-");
	else
		fc_result = format_procedure(fc_proid);

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		regprocedurerecv - 将外部二进制格式转换为 regprocedure
 */
Datum regprocedurerecv(PG_FUNCTION_ARGS)
{
	/* 完全与 oidrecv 相同，因此共享代码 */
	return oidrecv(fcinfo);
}

/*
 *		regproceduresend - 将 regprocedure 转换为二进制格式
 */
Datum regproceduresend(PG_FUNCTION_ARGS)
{
	/* 完全与 oidsend 相同，因此共享代码 */
	return oidsend(fcinfo);
}


/*
 * regoperin - 将 "oprname" 转换为 operator OID
 *
 * 我们也接受一个数字 OID，以便与输出例程对称。
 *
 * '0' 表示未知 (OID 0)。在所有其他情况下，输入必须
 * 与现有 pg_operator 条目匹配。
 */
Datum regoperin(PG_FUNCTION_ARGS)
{
	char	   *fc_opr_name_or_oid = PG_GETARG_CSTRING(0);
	Oid			fc_result = InvalidOid;
	List	   *fc_names;
	FuncCandidateList fc_clist;

	/* '0' ? */
	if (strcmp(fc_opr_name_or_oid, "0") == 0)
		PG_RETURN_OID(InvalidOid);

	/* 数字OID？ */
	if (fc_opr_name_or_oid[0] >= '0' &&
		fc_opr_name_or_oid[0] <= '9' &&
		strspn(fc_opr_name_or_oid, "0123456789") == strlen(fc_opr_name_or_oid))
	{
		fc_result = DatumGetObjectId(DirectFunctionCall1(oidin,
													  CStringGetDatum(fc_opr_name_or_oid)));
		PG_RETURN_OID(fc_result);
	}

	/* 否则它是一个名称，可能是模式限定的 */

	/* 此后的内容在引导模式下无法工作 */
	if (IsBootstrapProcessingMode())
		elog(ERROR, "regoper values must be OIDs in bootstrap mode");

	/*
	 * 正常情况：将名称解析为组件并查看它是否与当前搜索路径中的任何
	 * pg_operator 条目匹配。
	 */
	fc_names = stringToQualifiedNameList(fc_opr_name_or_oid);
	fc_clist = OpernameGetCandidates(fc_names, '\0', false);

	if (fc_clist == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("operator does not exist: %s", fc_opr_name_or_oid)));
	else if (fc_clist->next != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
				 errmsg("more than one operator named %s",
						fc_opr_name_or_oid)));

	fc_result = fc_clist->oid;

	PG_RETURN_OID(fc_result);
}

/*
 * to_regoper - 将 "oprname" 转换为 operator OID
 *
 * 如果未找到该名称，我们返回 NULL。
 */
Datum to_regoper(PG_FUNCTION_ARGS)
{
	char	   *fc_opr_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
	List	   *fc_names;
	FuncCandidateList fc_clist;

	/*
	 * 将名称解析为组件，并查看是否与当前搜索路径中的任何 pg_operator
	 * 条目匹配。
	 */
	fc_names = stringToQualifiedNameList(fc_opr_name);
	fc_clist = OpernameGetCandidates(fc_names, '\0', true);

	if (fc_clist == NULL || fc_clist->next != NULL)
		PG_RETURN_NULL();

	PG_RETURN_OID(fc_clist->oid);
}

/*
 * regoperout		- 将操作符 OID 转换为 "opr_name"
 */
Datum regoperout(PG_FUNCTION_ARGS)
{
	Oid			fc_oprid = PG_GETARG_OID(0);
	char	   *fc_result;
	HeapTuple	fc_opertup;

	if (fc_oprid == InvalidOid)
	{
		fc_result = pstrdup("0");
		PG_RETURN_CSTRING(fc_result);
	}

	fc_opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(fc_oprid));

	if (HeapTupleIsValid(fc_opertup))
	{
		Form_pg_operator fc_operform = (Form_pg_operator) GETSTRUCT(fc_opertup);
		char	   *fc_oprname = NameStr(fc_operform->oprname);

		/*
		 * 在引导模式下，跳过花哨的命名空间处理，仅返回
		 * 操作符名称。 （此路径只是为了调试输出而需要。）
		 */
		if (IsBootstrapProcessingMode())
			fc_result = pstrdup(fc_oprname);
		else
		{
			FuncCandidateList fc_clist;

			/*
			 * 这个操作符会被 regoperin 找到（唯一地）吗？如果没有，
			 * 则需要进行资格验证。
			 */
			fc_clist = OpernameGetCandidates(list_make1(makeString(fc_oprname)),
										  '\0', false);
			if (fc_clist != NULL && fc_clist->next == NULL &&
				fc_clist->oid == fc_oprid)
				fc_result = pstrdup(fc_oprname);
			else
			{
				const char *fc_nspname;

				fc_nspname = get_namespace_name(fc_operform->oprnamespace);
				fc_nspname = quote_identifier(fc_nspname);
				fc_result = (char *) palloc(strlen(fc_nspname) + strlen(fc_oprname) + 2);
				sprintf(fc_result, "%s.%s", fc_nspname, fc_oprname);
			}
		}

		ReleaseSysCache(fc_opertup);
	}
	else
	{
		/*
		 * 如果 OID 不匹配任何 pg_operator 条目，则以数字形式返回
		 */
		fc_result = (char *) palloc(NAMEDATALEN);
		snprintf(fc_result, NAMEDATALEN, "%u", fc_oprid);
	}

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		regoperrecv			- 将外部二进制格式转换为 regoper
 */
Datum regoperrecv(PG_FUNCTION_ARGS)
{
	/* 完全与 oidrecv 相同，因此共享代码 */
	return oidrecv(fcinfo);
}

/*
 *		regopersend			- 将 regoper 转换为二进制格式
 */
Datum regopersend(PG_FUNCTION_ARGS)
{
	/* 完全与 oidsend 相同，因此共享代码 */
	return oidsend(fcinfo);
}


/*
 * regoperatorin		- 将 "oprname(args)" 转换为操作符 OID
 *
 * 我们也接受数字 OID，以便与输出例程对称。
 *
 * '0' 表示未知（OID 0）。在所有其他情况下，输入必须
 * 匹配现有的 pg_operator 条目。
 */
Datum regoperatorin(PG_FUNCTION_ARGS)
{
	char	   *fc_opr_name_or_oid = PG_GETARG_CSTRING(0);
	Oid			fc_result;
	List	   *fc_names;
	int			fc_nargs;
	Oid			fc_argtypes[FUNC_MAX_ARGS];

	/* '0' ? */
	if (strcmp(fc_opr_name_or_oid, "0") == 0)
		PG_RETURN_OID(InvalidOid);

	/* 数字OID？ */
	if (fc_opr_name_or_oid[0] >= '0' &&
		fc_opr_name_or_oid[0] <= '9' &&
		strspn(fc_opr_name_or_oid, "0123456789") == strlen(fc_opr_name_or_oid))
	{
		fc_result = DatumGetObjectId(DirectFunctionCall1(oidin,
													  CStringGetDatum(fc_opr_name_or_oid)));
		PG_RETURN_OID(fc_result);
	}

	/* 此后的内容在引导模式下无法工作 */
	if (IsBootstrapProcessingMode())
		elog(ERROR, "regoperator values must be OIDs in bootstrap mode");

	/*
	 * 否则它是一个名称和参数。解析名称和参数，查找
	 * 当前命名空间搜索列表中的潜在匹配项，并扫描以查看
	 * 哪一个与给定的参数类型完全匹配。  (不会有
	 * 超过一个匹配。)
	 */
	fc_parseNameAndArgTypes(fc_opr_name_or_oid, true, &fc_names, &fc_nargs, fc_argtypes);
	if (fc_nargs == 1)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_PARAMETER),
				 errmsg("missing argument"),
				 errhint("Use NONE to denote the missing argument of a unary operator.")));
	if (fc_nargs != 2)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
				 errmsg("too many arguments"),
				 errhint("Provide two argument types for operator.")));

	fc_result = OpernameGetOprid(fc_names, fc_argtypes[0], fc_argtypes[1]);

	if (!OidIsValid(fc_result))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("operator does not exist: %s", fc_opr_name_or_oid)));

	PG_RETURN_OID(fc_result);
}

/*
 * to_regoperator	- 将 "oprname(args)" 转换为操作符 OID
 *
 * 如果未找到名称，我们返回 NULL。
 */
Datum to_regoperator(PG_FUNCTION_ARGS)
{
	char	   *fc_opr_name_or_oid = text_to_cstring(PG_GETARG_TEXT_PP(0));
	Oid			fc_result;
	List	   *fc_names;
	int			fc_nargs;
	Oid			fc_argtypes[FUNC_MAX_ARGS];

	/*
	 * 解析名称和参数，查找当前命名空间搜索列表中的潜在匹配项，
	 * 并扫描以查看哪一个与给定的参数类型完全匹配。    (不会有
	 * 超过一个匹配。)
	 */
	fc_parseNameAndArgTypes(fc_opr_name_or_oid, true, &fc_names, &fc_nargs, fc_argtypes);
	if (fc_nargs == 1)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_PARAMETER),
				 errmsg("missing argument"),
				 errhint("Use NONE to denote the missing argument of a unary operator.")));
	if (fc_nargs != 2)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
				 errmsg("too many arguments"),
				 errhint("Provide two argument types for operator.")));

	fc_result = OpernameGetOprid(fc_names, fc_argtypes[0], fc_argtypes[1]);

	if (!OidIsValid(fc_result))
		PG_RETURN_NULL();

	PG_RETURN_OID(fc_result);
}

/*
 * format_operator_extended - 将操作符 OID 转换为 "opr_name(args)"
 *
 * 这导出了 regoperatorout 的有用功能，以便在
 * 其他后端模块中使用。结果是 palloc'd 字符串，或 NULL。
 *
 * 'flags' 中的以下位修改行为：
 * - FORMAT_OPERATOR_INVALID_AS_NULL
 *			如果操作符 OID 无效或未知，则返回 NULL 而不是
 *			数字 OID。
 * - FORMAT_OPERATOR_FORCE_QUALIFY
 *			始终为操作符名称提供模式限定，而不论 search_path
 */
char * format_operator_extended(Oid fc_operator_oid, bits16 fc_flags)
{
	char	   *fc_result;
	HeapTuple	fc_opertup;

	fc_opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(fc_operator_oid));

	if (HeapTupleIsValid(fc_opertup))
	{
		Form_pg_operator fc_operform = (Form_pg_operator) GETSTRUCT(fc_opertup);
		char	   *fc_oprname = NameStr(fc_operform->oprname);
		char	   *fc_nspname;
		StringInfoData fc_buf;

		/* XXX 在引导模式下没有支持 */
		Assert(!IsBootstrapProcessingMode());

		initStringInfo(&fc_buf);

		/*
		 * 这项操作符会被 regoperatorin 找到（给定正确的参数）吗？
		 * 如果没有，或者调用方明确要求，我们需要进行资格验证。
		 */
		if ((fc_flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ||
			!OperatorIsVisible(fc_operator_oid))
		{
			fc_nspname = get_namespace_name(fc_operform->oprnamespace);
			appendStringInfo(&fc_buf, "%s.",
							 quote_identifier(fc_nspname));
		}

		appendStringInfo(&fc_buf, "%s(", fc_oprname);

		if (fc_operform->oprleft)
			appendStringInfo(&fc_buf, "%s,",
							 (fc_flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
							 format_type_be_qualified(fc_operform->oprleft) :
							 format_type_be(fc_operform->oprleft));
		else
			appendStringInfoString(&fc_buf, "NONE,");

		if (fc_operform->oprright)
			appendStringInfo(&fc_buf, "%s)",
							 (fc_flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
							 format_type_be_qualified(fc_operform->oprright) :
							 format_type_be(fc_operform->oprright));
		else
			appendStringInfoString(&fc_buf, "NONE)");

		fc_result = fc_buf.data;

		ReleaseSysCache(fc_opertup);
	}
	else if ((fc_flags & FORMAT_OPERATOR_INVALID_AS_NULL) != 0)
	{
		/* 如果对象未定义，返回 NULL，正如调用者所需 */
		fc_result = NULL;
	}
	else
	{
		/*
		 * 如果 OID 不匹配任何 pg_operator 条目，则以数字形式返回
		 */
		fc_result = (char *) palloc(NAMEDATALEN);
		snprintf(fc_result, NAMEDATALEN, "%u", fc_operator_oid);
	}

	return fc_result;
}

char * format_operator(Oid fc_operator_oid)
{
	return format_operator_extended(fc_operator_oid, 0);
}

char * format_operator_qualified(Oid fc_operator_oid)
{
	return format_operator_extended(fc_operator_oid,
									FORMAT_OPERATOR_FORCE_QUALIFY);
}

void format_operator_parts(Oid fc_operator_oid, List **fc_objnames, List **fc_objargs,
					  bool fc_missing_ok)
{
	HeapTuple	fc_opertup;
	Form_pg_operator fc_oprForm;

	fc_opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(fc_operator_oid));
	if (!HeapTupleIsValid(fc_opertup))
	{
		if (!fc_missing_ok)
			elog(ERROR, "cache lookup failed for operator with OID %u",
				 fc_operator_oid);
		return;
	}

	fc_oprForm = (Form_pg_operator) GETSTRUCT(fc_opertup);
	*fc_objnames = list_make2(get_namespace_name_or_temp(fc_oprForm->oprnamespace),
						   pstrdup(NameStr(fc_oprForm->oprname)));
	*fc_objargs = NIL;
	if (fc_oprForm->oprleft)
		*fc_objargs = lappend(*fc_objargs,
						   format_type_be_qualified(fc_oprForm->oprleft));
	if (fc_oprForm->oprright)
		*fc_objargs = lappend(*fc_objargs,
						   format_type_be_qualified(fc_oprForm->oprright));

	ReleaseSysCache(fc_opertup);
}

/*
 * regoperatorout		- 将操作符 OID 转换为 "opr_name(args)"
 */
Datum regoperatorout(PG_FUNCTION_ARGS)
{
	Oid			fc_oprid = PG_GETARG_OID(0);
	char	   *fc_result;

	if (fc_oprid == InvalidOid)
		fc_result = pstrdup("0");
	else
		fc_result = format_operator(fc_oprid);

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		regoperatorrecv			- 将外部二进制格式转换为 regoperator
 */
Datum regoperatorrecv(PG_FUNCTION_ARGS)
{
	/* 完全与 oidrecv 相同，因此共享代码 */
	return oidrecv(fcinfo);
}

/*
 *		regoperatorsend			- 将 regoperator 转换为二进制格式
 */
Datum regoperatorsend(PG_FUNCTION_ARGS)
{
	/* 完全与 oidsend 相同，因此共享代码 */
	return oidsend(fcinfo);
}


/*
 * regclassin		- 将 "classname" 转换为类 OID
 *
 * 我们也接受数字 OID，以便与输出例程对称。
 *
 * '-' 表示未知（OID 0）。在所有其他情况下，输入必须
 * 匹配现有的 pg_class 条目。
 */
Datum regclassin(PG_FUNCTION_ARGS)
{
	char	   *fc_class_name_or_oid = PG_GETARG_CSTRING(0);
	Oid			fc_result = InvalidOid;
	List	   *fc_names;

	/* '-' ? */
	if (strcmp(fc_class_name_or_oid, "-") == 0)
		PG_RETURN_OID(InvalidOid);

	/* 数字OID？ */
	if (fc_class_name_or_oid[0] >= '0' &&
		fc_class_name_or_oid[0] <= '9' &&
		strspn(fc_class_name_or_oid, "0123456789") == strlen(fc_class_name_or_oid))
	{
		fc_result = DatumGetObjectId(DirectFunctionCall1(oidin,
													  CStringGetDatum(fc_class_name_or_oid)));
		PG_RETURN_OID(fc_result);
	}

	/* 否则它是一个名称，可能是模式限定的 */

	/* 此后的内容在引导模式下无法工作 */
	if (IsBootstrapProcessingMode())
		elog(ERROR, "regclass values must be OIDs in bootstrap mode");

	/*
	 * 正常情况：将名称解析为组件，并查看是否与任何
	 * pg_class 条目在当前搜索路径中匹配。
	 */
	fc_names = stringToQualifiedNameList(fc_class_name_or_oid);

	/* 我们可能甚至没有此关系的权限；不要锁定它。 */
	fc_result = RangeVarGetRelid(makeRangeVarFromNameList(fc_names), NoLock, false);

	PG_RETURN_OID(fc_result);
}

/*
 * to_regclass		- 将 "classname" 转换为类 OID
 *
 * 如果未找到名称，我们返回 NULL。
 */
Datum to_regclass(PG_FUNCTION_ARGS)
{
	char	   *fc_class_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
	Oid			fc_result;
	List	   *fc_names;

	/*
	 * 将名称解析为组件，并查看是否与任何 pg_class
	 * 条目在当前搜索路径中匹配。
	 */
	fc_names = stringToQualifiedNameList(fc_class_name);

	/* 我们可能甚至没有此关系的权限；不要锁定它。 */
	fc_result = RangeVarGetRelid(makeRangeVarFromNameList(fc_names), NoLock, true);

	if (OidIsValid(fc_result))
		PG_RETURN_OID(fc_result);
	else
		PG_RETURN_NULL();
}

/*
 * regclassout		- 将类 OID 转换为 "class_name"
 */
Datum regclassout(PG_FUNCTION_ARGS)
{
	Oid			fc_classid = PG_GETARG_OID(0);
	char	   *fc_result;
	HeapTuple	fc_classtup;

	if (fc_classid == InvalidOid)
	{
		fc_result = pstrdup("-");
		PG_RETURN_CSTRING(fc_result);
	}

	fc_classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_classid));

	if (HeapTupleIsValid(fc_classtup))
	{
		Form_pg_class fc_classform = (Form_pg_class) GETSTRUCT(fc_classtup);
		char	   *fc_classname = NameStr(fc_classform->relname);

		/*
		 * 在引导模式下，跳过花哨的命名空间处理，仅返回
		 * 类名称。 （此路径只是为了调试输出而需要。）
		 */
		if (IsBootstrapProcessingMode())
			fc_result = pstrdup(fc_classname);
		else
		{
			char	   *fc_nspname;

			/*
			 * 这个类会被 regclassin 找到吗？如果没有，则进行资格验证。
			 */
			if (RelationIsVisible(fc_classid))
				fc_nspname = NULL;
			else
				fc_nspname = get_namespace_name(fc_classform->relnamespace);

			fc_result = quote_qualified_identifier(fc_nspname, fc_classname);
		}

		ReleaseSysCache(fc_classtup);
	}
	else
	{
		/* 如果 OID 不匹配任何 pg_class 条目，则以数字形式返回 */
		fc_result = (char *) palloc(NAMEDATALEN);
		snprintf(fc_result, NAMEDATALEN, "%u", fc_classid);
	}

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		regclassrecv			- 将外部二进制格式转换为 regclass
 */
Datum regclassrecv(PG_FUNCTION_ARGS)
{
	/* 完全与 oidrecv 相同，因此共享代码 */
	return oidrecv(fcinfo);
}

/*
 *		regclasssend			- 将 regclass 转换为二进制格式
 */
Datum regclasssend(PG_FUNCTION_ARGS)
{
	/* 完全与 oidsend 相同，因此共享代码 */
	return oidsend(fcinfo);
}


/*
 * regcollationin		- 将 "collationname" 转换为排序规则 OID
 *
 * 我们也接受数字 OID，以便与输出例程对称。
 *
 * '-' 表示未知（OID 0）。在所有其他情况下，输入必须
 * 匹配现有的 pg_collation 条目。
 */
Datum regcollationin(PG_FUNCTION_ARGS)
{
	char	   *fc_collation_name_or_oid = PG_GETARG_CSTRING(0);
	Oid			fc_result = InvalidOid;
	List	   *fc_names;

	/* '-' ? */
	if (strcmp(fc_collation_name_or_oid, "-") == 0)
		PG_RETURN_OID(InvalidOid);

	/* 数字OID？ */
	if (fc_collation_name_or_oid[0] >= '0' &&
		fc_collation_name_or_oid[0] <= '9' &&
		strspn(fc_collation_name_or_oid, "0123456789") == strlen(fc_collation_name_or_oid))
	{
		fc_result = DatumGetObjectId(DirectFunctionCall1(oidin,
													  CStringGetDatum(fc_collation_name_or_oid)));
		PG_RETURN_OID(fc_result);
	}

	/* 否则它是一个名称，可能是模式限定的 */

	/* 此后的内容在引导模式下无法工作 */
	if (IsBootstrapProcessingMode())
		elog(ERROR, "regcollation values must be OIDs in bootstrap mode");

	/*
	 * 正常情况：将名称解析为组件，并查看是否与任何
	 * pg_collation 条目在当前搜索路径中匹配。
	 */
	fc_names = stringToQualifiedNameList(fc_collation_name_or_oid);

	fc_result = get_collation_oid(fc_names, false);

	PG_RETURN_OID(fc_result);
}

/*
 * to_regcollation		- 将 "collationname" 转换为排序规则 OID
 *
 * 如果未找到名称，我们返回 NULL。
 */
Datum to_regcollation(PG_FUNCTION_ARGS)
{
	char	   *fc_collation_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
	Oid			fc_result;
	List	   *fc_names;

	/*
	 * 将名称解析为组件，并查看是否与任何 pg_collation
	 * 条目在当前搜索路径中匹配。
	 */
	fc_names = stringToQualifiedNameList(fc_collation_name);

	/* 我们可能甚至没有此关系的权限；不要锁定它。 */
	fc_result = get_collation_oid(fc_names, true);

	if (OidIsValid(fc_result))
		PG_RETURN_OID(fc_result);
	else
		PG_RETURN_NULL();
}

/*
 * regcollationout		- 将排序规则 OID 转换为 "collation_name"
 */
Datum regcollationout(PG_FUNCTION_ARGS)
{
	Oid			fc_collationid = PG_GETARG_OID(0);
	char	   *fc_result;
	HeapTuple	fc_collationtup;

	if (fc_collationid == InvalidOid)
	{
		fc_result = pstrdup("-");
		PG_RETURN_CSTRING(fc_result);
	}

	fc_collationtup = SearchSysCache1(COLLOID, ObjectIdGetDatum(fc_collationid));

	if (HeapTupleIsValid(fc_collationtup))
	{
		Form_pg_collation fc_collationform = (Form_pg_collation) GETSTRUCT(fc_collationtup);
		char	   *fc_collationname = NameStr(fc_collationform->collname);

		/*
		 * 在引导模式下，跳过花哨的命名空间处理，仅返回
		 * 排序规则名称。 （此路径只是为了调试输出而需要。）
		 */
		if (IsBootstrapProcessingMode())
			fc_result = pstrdup(fc_collationname);
		else
		{
			char	   *fc_nspname;

			/*
			 * 该排序是否会被 regcollationin 找到？如果没有，请限定它。
			 */
			if (CollationIsVisible(fc_collationid))
				fc_nspname = NULL;
			else
				fc_nspname = get_namespace_name(fc_collationform->collnamespace);

			fc_result = quote_qualified_identifier(fc_nspname, fc_collationname);
		}

		ReleaseSysCache(fc_collationtup);
	}
	else
	{
		/* 如果 OID 与任何 pg_collation 条目不匹配，则以数字形式返回它 */
		fc_result = (char *) palloc(NAMEDATALEN);
		snprintf(fc_result, NAMEDATALEN, "%u", fc_collationid);
	}

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		regcollationrecv			- 将外部二进制格式转换为 regcollation
 */
Datum regcollationrecv(PG_FUNCTION_ARGS)
{
	/* 完全与 oidrecv 相同，因此共享代码 */
	return oidrecv(fcinfo);
}

/*
 *		regcollationsend			- 将 regcollation 转换为二进制格式
 */
Datum regcollationsend(PG_FUNCTION_ARGS)
{
	/* 完全与 oidsend 相同，因此共享代码 */
	return oidsend(fcinfo);
}


/*
 * regtypein		- 将 "typename" 转换为类型 OID
 *
 * 类型名称可以使用解析器识别的完整类型语法进行指定；例如，DOUBLE PRECISION 和 INTEGER[] 将有效并被转换为正确的类型名称。 （我们忽略解析器生成的任何 typmod 信息。）
 *
 * 我们也接受数字 OID，以与输出例程保持对称，并可能在引导模式下使用。
 *
 * '-' 表示未知（OID 0）。在所有其他情况下，输入必须与现有的 pg_type 条目匹配。
 */
Datum regtypein(PG_FUNCTION_ARGS)
{
	char	   *fc_typ_name_or_oid = PG_GETARG_CSTRING(0);
	Oid			fc_result = InvalidOid;
	int32		fc_typmod;

	/* '-' ? */
	if (strcmp(fc_typ_name_or_oid, "-") == 0)
		PG_RETURN_OID(InvalidOid);

	/* 数字OID？ */
	if (fc_typ_name_or_oid[0] >= '0' &&
		fc_typ_name_or_oid[0] <= '9' &&
		strspn(fc_typ_name_or_oid, "0123456789") == strlen(fc_typ_name_or_oid))
	{
		fc_result = DatumGetObjectId(DirectFunctionCall1(oidin,
													  CStringGetDatum(fc_typ_name_or_oid)));
		PG_RETURN_OID(fc_result);
	}

	/* 否则它是一个类型名称，可能带有模式限定或装饰 */

	/* 此后的内容在引导模式下无法工作 */
	if (IsBootstrapProcessingMode())
		elog(ERROR, "regtype values must be OIDs in bootstrap mode");

	/*
	 * 正常情况：调用完整的解析器来处理数组语法等特殊情况。
	 */
	parseTypeString(fc_typ_name_or_oid, &fc_result, &fc_typmod, false);

	PG_RETURN_OID(fc_result);
}

/*
 * to_regtype		- 将 "typename" 转换为类型 OID
 *
 * 如果未找到名称，则返回 NULL。
 */
Datum to_regtype(PG_FUNCTION_ARGS)
{
	char	   *fc_typ_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
	Oid			fc_result;
	int32		fc_typmod;

	/*
	 * 调用完整的解析器来处理数组语法等特殊情况。
	 */
	parseTypeString(fc_typ_name, &fc_result, &fc_typmod, true);

	if (OidIsValid(fc_result))
		PG_RETURN_OID(fc_result);
	else
		PG_RETURN_NULL();
}

/*
 * regtypeout		- 将类型 OID 转换为 "typ_name"
 */
Datum regtypeout(PG_FUNCTION_ARGS)
{
	Oid			fc_typid = PG_GETARG_OID(0);
	char	   *fc_result;
	HeapTuple	fc_typetup;

	if (fc_typid == InvalidOid)
	{
		fc_result = pstrdup("-");
		PG_RETURN_CSTRING(fc_result);
	}

	fc_typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_typid));

	if (HeapTupleIsValid(fc_typetup))
	{
		Form_pg_type fc_typeform = (Form_pg_type) GETSTRUCT(fc_typetup);

		/*
		 * 在引导模式下，跳过华丽的命名空间内容，仅返回类型名称。 （这个路径仅在调试输出时需要。）
		 */
		if (IsBootstrapProcessingMode())
		{
			char	   *fc_typname = NameStr(fc_typeform->typname);

			fc_result = pstrdup(fc_typname);
		}
		else
			fc_result = format_type_be(fc_typid);

		ReleaseSysCache(fc_typetup);
	}
	else
	{
		/* 如果 OID 与任何 pg_type 条目不匹配，则以数字形式返回它 */
		fc_result = (char *) palloc(NAMEDATALEN);
		snprintf(fc_result, NAMEDATALEN, "%u", fc_typid);
	}

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		regtyperecv			- 将外部二进制格式转换为 regtype
 */
Datum regtyperecv(PG_FUNCTION_ARGS)
{
	/* 完全与 oidrecv 相同，因此共享代码 */
	return oidrecv(fcinfo);
}

/*
 *		regtypesend			- 将 regtype 转换为二进制格式
 */
Datum regtypesend(PG_FUNCTION_ARGS)
{
	/* 完全与 oidsend 相同，因此共享代码 */
	return oidsend(fcinfo);
}


/*
 * regconfigin		- 将 "tsconfigname" 转换为 tsconfig OID
 *
 * 我们也接受数字 OID，以与输出例程保持对称。
 *
 * '-' 表示未知（OID 0）。在所有其他情况下，输入必须与现有的 pg_ts_config 条目匹配。
 */
Datum regconfigin(PG_FUNCTION_ARGS)
{
	char	   *fc_cfg_name_or_oid = PG_GETARG_CSTRING(0);
	Oid			fc_result;
	List	   *fc_names;

	/* '-' ? */
	if (strcmp(fc_cfg_name_or_oid, "-") == 0)
		PG_RETURN_OID(InvalidOid);

	/* 数字OID？ */
	if (fc_cfg_name_or_oid[0] >= '0' &&
		fc_cfg_name_or_oid[0] <= '9' &&
		strspn(fc_cfg_name_or_oid, "0123456789") == strlen(fc_cfg_name_or_oid))
	{
		fc_result = DatumGetObjectId(DirectFunctionCall1(oidin,
													  CStringGetDatum(fc_cfg_name_or_oid)));
		PG_RETURN_OID(fc_result);
	}

	/* 此后的内容在引导模式下无法工作 */
	if (IsBootstrapProcessingMode())
		elog(ERROR, "regconfig values must be OIDs in bootstrap mode");

	/*
	 * 正常情况：将名称解析为组件，并查看它是否与当前搜索路径中的任何 pg_ts_config 条目匹配。
	 */
	fc_names = stringToQualifiedNameList(fc_cfg_name_or_oid);

	fc_result = get_ts_config_oid(fc_names, false);

	PG_RETURN_OID(fc_result);
}

/*
 * regconfigout		- 将 tsconfig OID 转换为 "tsconfigname"
 */
Datum regconfigout(PG_FUNCTION_ARGS)
{
	Oid			fc_cfgid = PG_GETARG_OID(0);
	char	   *fc_result;
	HeapTuple	fc_cfgtup;

	if (fc_cfgid == InvalidOid)
	{
		fc_result = pstrdup("-");
		PG_RETURN_CSTRING(fc_result);
	}

	fc_cfgtup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(fc_cfgid));

	if (HeapTupleIsValid(fc_cfgtup))
	{
		Form_pg_ts_config fc_cfgform = (Form_pg_ts_config) GETSTRUCT(fc_cfgtup);
		char	   *fc_cfgname = NameStr(fc_cfgform->cfgname);
		char	   *fc_nspname;

		/*
		 * 该配置是否会被 regconfigin 找到？如果没有，请限定它。
		 */
		if (TSConfigIsVisible(fc_cfgid))
			fc_nspname = NULL;
		else
			fc_nspname = get_namespace_name(fc_cfgform->cfgnamespace);

		fc_result = quote_qualified_identifier(fc_nspname, fc_cfgname);

		ReleaseSysCache(fc_cfgtup);
	}
	else
	{
		/* 如果 OID 与任何 pg_ts_config 行不匹配，则以数字形式返回它 */
		fc_result = (char *) palloc(NAMEDATALEN);
		snprintf(fc_result, NAMEDATALEN, "%u", fc_cfgid);
	}

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		regconfigrecv			- 将外部二进制格式转换为 regconfig
 */
Datum regconfigrecv(PG_FUNCTION_ARGS)
{
	/* 完全与 oidrecv 相同，因此共享代码 */
	return oidrecv(fcinfo);
}

/*
 *		regconfigsend			- 将 regconfig 转换为二进制格式
 */
Datum regconfigsend(PG_FUNCTION_ARGS)
{
	/* 完全与 oidsend 相同，因此共享代码 */
	return oidsend(fcinfo);
}


/*
 * regdictionaryin		- 将 "tsdictionaryname" 转换为 tsdictionary OID
 *
 * 我们也接受数字 OID，以与输出例程保持对称。
 *
 * '-' 表示未知（OID 0）。在所有其他情况下，输入必须与现有的 pg_ts_dict 条目匹配。
 */
Datum regdictionaryin(PG_FUNCTION_ARGS)
{
	char	   *fc_dict_name_or_oid = PG_GETARG_CSTRING(0);
	Oid			fc_result;
	List	   *fc_names;

	/* '-' ? */
	if (strcmp(fc_dict_name_or_oid, "-") == 0)
		PG_RETURN_OID(InvalidOid);

	/* 数字OID？ */
	if (fc_dict_name_or_oid[0] >= '0' &&
		fc_dict_name_or_oid[0] <= '9' &&
		strspn(fc_dict_name_or_oid, "0123456789") == strlen(fc_dict_name_or_oid))
	{
		fc_result = DatumGetObjectId(DirectFunctionCall1(oidin,
													  CStringGetDatum(fc_dict_name_or_oid)));
		PG_RETURN_OID(fc_result);
	}

	/* 此后的内容在引导模式下无法工作 */
	if (IsBootstrapProcessingMode())
		elog(ERROR, "regdictionary values must be OIDs in bootstrap mode");

	/*
	 * 正常情况：将名称解析为组件，并查看它是否与当前搜索路径中的任何 pg_ts_dict 条目匹配。
	 */
	fc_names = stringToQualifiedNameList(fc_dict_name_or_oid);

	fc_result = get_ts_dict_oid(fc_names, false);

	PG_RETURN_OID(fc_result);
}

/*
 * regdictionaryout		- 将 tsdictionary OID 转换为 "tsdictionaryname"
 */
Datum regdictionaryout(PG_FUNCTION_ARGS)
{
	Oid			fc_dictid = PG_GETARG_OID(0);
	char	   *fc_result;
	HeapTuple	fc_dicttup;

	if (fc_dictid == InvalidOid)
	{
		fc_result = pstrdup("-");
		PG_RETURN_CSTRING(fc_result);
	}

	fc_dicttup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(fc_dictid));

	if (HeapTupleIsValid(fc_dicttup))
	{
		Form_pg_ts_dict fc_dictform = (Form_pg_ts_dict) GETSTRUCT(fc_dicttup);
		char	   *fc_dictname = NameStr(fc_dictform->dictname);
		char	   *fc_nspname;

		/*
		 * 该字典是否会被 regdictionaryin 找到？如果没有，请限定它。
		 */
		if (TSDictionaryIsVisible(fc_dictid))
			fc_nspname = NULL;
		else
			fc_nspname = get_namespace_name(fc_dictform->dictnamespace);

		fc_result = quote_qualified_identifier(fc_nspname, fc_dictname);

		ReleaseSysCache(fc_dicttup);
	}
	else
	{
		/* 如果 OID 与任何 pg_ts_dict 行不匹配，则以数字形式返回它 */
		fc_result = (char *) palloc(NAMEDATALEN);
		snprintf(fc_result, NAMEDATALEN, "%u", fc_dictid);
	}

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		regdictionaryrecv	- 将外部二进制格式转换为 regdictionary
 */
Datum regdictionaryrecv(PG_FUNCTION_ARGS)
{
	/* 完全与 oidrecv 相同，因此共享代码 */
	return oidrecv(fcinfo);
}

/*
 *		regdictionarysend	- 将 regdictionary 转换为二进制格式
 */
Datum regdictionarysend(PG_FUNCTION_ARGS)
{
	/* 完全与 oidsend 相同，因此共享代码 */
	return oidsend(fcinfo);
}

/*
 * regrolein	- 将 "rolename" 转换为角色 OID
 *
 * 我们也接受数字 OID，以与输出例程保持对称。
 *
 * '-' 表示未知（OID 0）。在所有其他情况下，输入必须与现有的 pg_authid 条目匹配。
 */
Datum regrolein(PG_FUNCTION_ARGS)
{
	char	   *fc_role_name_or_oid = PG_GETARG_CSTRING(0);
	Oid			fc_result;
	List	   *fc_names;

	/* '-' ? */
	if (strcmp(fc_role_name_or_oid, "-") == 0)
		PG_RETURN_OID(InvalidOid);

	/* 数字OID？ */
	if (fc_role_name_or_oid[0] >= '0' &&
		fc_role_name_or_oid[0] <= '9' &&
		strspn(fc_role_name_or_oid, "0123456789") == strlen(fc_role_name_or_oid))
	{
		fc_result = DatumGetObjectId(DirectFunctionCall1(oidin,
													  CStringGetDatum(fc_role_name_or_oid)));
		PG_RETURN_OID(fc_result);
	}

	/* 此后的内容在引导模式下无法工作 */
	if (IsBootstrapProcessingMode())
		elog(ERROR, "regrole values must be OIDs in bootstrap mode");

	/* 正常情况：查看名称是否与任何 pg_authid 条目匹配。 */
	fc_names = stringToQualifiedNameList(fc_role_name_or_oid);

	if (list_length(fc_names) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("invalid name syntax")));

	fc_result = get_role_oid(strVal(linitial(fc_names)), false);

	PG_RETURN_OID(fc_result);
}

/*******************************
 * to_regrole		- 将“rolename”转换为角色OID
 *
 * 如果未找到名称，我们返回NULL。
 */
Datum to_regrole(PG_FUNCTION_ARGS)
{
	char	   *fc_role_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
	Oid			fc_result;
	List	   *fc_names;

	fc_names = stringToQualifiedNameList(fc_role_name);

	if (list_length(fc_names) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("invalid name syntax")));

	fc_result = get_role_oid(strVal(linitial(fc_names)), true);

	if (OidIsValid(fc_result))
		PG_RETURN_OID(fc_result);
	else
		PG_RETURN_NULL();
}

/*
 * regroleout		- 将角色OID转换为“role_name”
 */
Datum regroleout(PG_FUNCTION_ARGS)
{
	Oid			fc_roleoid = PG_GETARG_OID(0);
	char	   *fc_result;

	if (fc_roleoid == InvalidOid)
	{
		fc_result = pstrdup("-");
		PG_RETURN_CSTRING(fc_result);
	}

	fc_result = GetUserNameFromId(fc_roleoid, true);

	if (fc_result)
	{
		/* pstrdup 其实并不是必需的，但可以避免编译警告 */
		fc_result = pstrdup(quote_identifier(fc_result));
	}
	else
	{
		/* 如果OID与任何角色不匹配，则以数字形式返回 */
		fc_result = (char *) palloc(NAMEDATALEN);
		snprintf(fc_result, NAMEDATALEN, "%u", fc_roleoid);
	}

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		regrolerecv - 将外部二进制格式转换为regrole
 */
Datum regrolerecv(PG_FUNCTION_ARGS)
{
	/* 完全与 oidrecv 相同，因此共享代码 */
	return oidrecv(fcinfo);
}

/*
 *		regrolesend - 将regrole转换为二进制格式
 */
Datum regrolesend(PG_FUNCTION_ARGS)
{
	/* 完全与 oidsend 相同，因此共享代码 */
	return oidsend(fcinfo);
}

/*
 * regnamespacein		- 将“nspname”转换为命名空间OID
 *
 * 我们还接受数字OID，以便与输出例程保持对称。
 *
 * '-' 表示未知 (OID 0)。在所有其他情况下，输入必须
 * 匹配现有的pg_namespace条目。
 */
Datum regnamespacein(PG_FUNCTION_ARGS)
{
	char	   *fc_nsp_name_or_oid = PG_GETARG_CSTRING(0);
	Oid			fc_result;
	List	   *fc_names;

	/* '-' ? */
	if (strcmp(fc_nsp_name_or_oid, "-") == 0)
		PG_RETURN_OID(InvalidOid);

	/* 数字OID？ */
	if (fc_nsp_name_or_oid[0] >= '0' &&
		fc_nsp_name_or_oid[0] <= '9' &&
		strspn(fc_nsp_name_or_oid, "0123456789") == strlen(fc_nsp_name_or_oid))
	{
		fc_result = DatumGetObjectId(DirectFunctionCall1(oidin,
													  CStringGetDatum(fc_nsp_name_or_oid)));
		PG_RETURN_OID(fc_result);
	}

	/* 此后的内容在引导模式下无法工作 */
	if (IsBootstrapProcessingMode())
		elog(ERROR, "regnamespace values must be OIDs in bootstrap mode");

	/* 正常情况：查看名称是否匹配任何pg_namespace条目。 */
	fc_names = stringToQualifiedNameList(fc_nsp_name_or_oid);

	if (list_length(fc_names) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("invalid name syntax")));

	fc_result = get_namespace_oid(strVal(linitial(fc_names)), false);

	PG_RETURN_OID(fc_result);
}

/*
 * to_regnamespace		- 将“nspname”转换为命名空间OID
 *
 * 如果未找到名称，我们返回NULL。
 */
Datum to_regnamespace(PG_FUNCTION_ARGS)
{
	char	   *fc_nsp_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
	Oid			fc_result;
	List	   *fc_names;

	fc_names = stringToQualifiedNameList(fc_nsp_name);

	if (list_length(fc_names) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("invalid name syntax")));

	fc_result = get_namespace_oid(strVal(linitial(fc_names)), true);

	if (OidIsValid(fc_result))
		PG_RETURN_OID(fc_result);
	else
		PG_RETURN_NULL();
}

/*
 * regnamespaceout		- 将命名空间OID转换为“nsp_name”
 */
Datum regnamespaceout(PG_FUNCTION_ARGS)
{
	Oid			fc_nspid = PG_GETARG_OID(0);
	char	   *fc_result;

	if (fc_nspid == InvalidOid)
	{
		fc_result = pstrdup("-");
		PG_RETURN_CSTRING(fc_result);
	}

	fc_result = get_namespace_name(fc_nspid);

	if (fc_result)
	{
		/* pstrdup 其实并不是必需的，但可以避免编译警告 */
		fc_result = pstrdup(quote_identifier(fc_result));
	}
	else
	{
		/* 如果OID与任何命名空间不匹配，则以数字形式返回 */
		fc_result = (char *) palloc(NAMEDATALEN);
		snprintf(fc_result, NAMEDATALEN, "%u", fc_nspid);
	}

	PG_RETURN_CSTRING(fc_result);
}

/*
 *		regnamespacerecv	- 将外部二进制格式转换为regnamespace
 */
Datum regnamespacerecv(PG_FUNCTION_ARGS)
{
	/* 完全与 oidrecv 相同，因此共享代码 */
	return oidrecv(fcinfo);
}

/*
 *		regnamespacesend		- 将regnamespace转换为二进制格式
 */
Datum regnamespacesend(PG_FUNCTION_ARGS)
{
	/* 完全与 oidsend 相同，因此共享代码 */
	return oidsend(fcinfo);
}

/*
 * text_regclass: 将文本转换为regclass
 *
 * 这可以被CoerceViaIO替代，只是我们需要将
 * 文本转换为regclass视为隐式转换，以支持nextval()及
 * 相关函数的遗留形式。
 */
Datum text_regclass(PG_FUNCTION_ARGS)
{
	text	   *fc_relname = PG_GETARG_TEXT_PP(0);
	Oid			fc_result;
	RangeVar   *fc_rv;

	fc_rv = makeRangeVarFromNameList(textToQualifiedNameList(fc_relname));

	/* 我们可能甚至没有此关系的权限；不要锁定它。 */
	fc_result = RangeVarGetRelid(fc_rv, NoLock, false);

	PG_RETURN_OID(fc_result);
}


/*
 * 给定一个C字符串，将其解析为一个合格的名称列表。
 */
List * stringToQualifiedNameList(const char *fc_string)
{
	char	   *fc_rawname;
	List	   *fc_result = NIL;
	List	   *fc_namelist;
	ListCell   *fc_l;

	/* 我们需要输入字符串的可修改副本。 */
	fc_rawname = pstrdup(fc_string);

	if (!SplitIdentifierString(fc_rawname, '.', &fc_namelist))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("invalid name syntax")));

	if (fc_namelist == NIL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("invalid name syntax")));

	foreach(fc_l, fc_namelist)
	{
		char	   *fc_curname = (char *) lfirst(fc_l);

		fc_result = lappend(fc_result, makeString(pstrdup(fc_curname)));
	}

	pfree(fc_rawname);
	list_free(fc_namelist);

	return fc_result;
}

/*****************************************************************************
 *	 支持例程														 *
 *****************************************************************************/

/*
 * 给定一个C字符串，将其解析为一个合格的函数或操作符名称，
 * 后跟一个括号中的类型名称列表。将类型名称简化为OID数组
 * （返回到*nargs和*argtypes；argtypes数组的大小应为FUNC_MAX_ARGS）。
 * 函数或操作符名称作为字符串列表返回到*names。
 *
 * 如果allowNone为真，则接受“NONE”并将其返回为InvalidOid（这是
 * 针对一元操作符）。
 */
static void fc_parseNameAndArgTypes(const char *fc_string, bool fc_allowNone, List **fc_names,
					 int *fc_nargs, Oid *fc_argtypes)
{
	char	   *fc_rawname;
	char	   *fc_ptr;
	char	   *fc_ptr2;
	char	   *fc_typename;
	bool		fc_in_quote;
	bool		fc_had_comma;
	int			fc_paren_count;
	Oid			fc_typeid;
	int32		fc_typmod;

	/* 我们需要输入字符串的可修改副本。 */
	fc_rawname = pstrdup(fc_string);

	/* 扫描以找到预期的左括号；不能被引号包围 */
	fc_in_quote = false;
	for (fc_ptr = fc_rawname; *fc_ptr; fc_ptr++)
	{
		if (*fc_ptr == '"')
			fc_in_quote = !fc_in_quote;
		else if (*fc_ptr == '(' && !fc_in_quote)
			break;
	}
	if (*fc_ptr == '\0')
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("expected a left parenthesis")));

	/* 分离名称并将其解析为列表 */
	*fc_ptr++ = '\0';
	*fc_names = stringToQualifiedNameList(fc_rawname);

	/* 检查尾部右括号并将其移除 */
	fc_ptr2 = fc_ptr + strlen(fc_ptr);
	while (--fc_ptr2 > fc_ptr)
	{
		if (!scanner_isspace(*fc_ptr2))
			break;
	}
	if (*fc_ptr2 != ')')
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("expected a right parenthesis")));

	*fc_ptr2 = '\0';

	/* 将剩余字符串分离为以逗号分隔的类型名称 */
	*fc_nargs = 0;
	fc_had_comma = false;

	for (;;)
	{
		/* 允许前导空格 */
		while (scanner_isspace(*fc_ptr))
			fc_ptr++;
		if (*fc_ptr == '\0')
		{
			/* 字符串结束。除非之前有逗号，否则可以。 */
			if (fc_had_comma)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
						 errmsg("expected a type name")));
			break;
		}
		fc_typename = fc_ptr;
		/* 查找类型名称的结束 --- 字符串末尾或逗号 */
		/* ... 但不是被引号或括号括起来的逗号 */
		fc_in_quote = false;
		fc_paren_count = 0;
		for (; *fc_ptr; fc_ptr++)
		{
			if (*fc_ptr == '"')
				fc_in_quote = !fc_in_quote;
			else if (*fc_ptr == ',' && !fc_in_quote && fc_paren_count == 0)
				break;
			else if (!fc_in_quote)
			{
				switch (*fc_ptr)
				{
					case '(':
					case '[':
						fc_paren_count++;
						break;
					case ')':
					case ']':
						fc_paren_count--;
						break;
				}
			}
		}
		if (fc_in_quote || fc_paren_count != 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
					 errmsg("improper type name")));

		fc_ptr2 = fc_ptr;
		if (*fc_ptr == ',')
		{
			fc_had_comma = true;
			*fc_ptr++ = '\0';
		}
		else
		{
			fc_had_comma = false;
			Assert(*fc_ptr == '\0');
		}
		/* 剪掉尾部空格 */
		while (--fc_ptr2 >= fc_typename)
		{
			if (!scanner_isspace(*fc_ptr2))
				break;
			*fc_ptr2 = '\0';
		}

		if (fc_allowNone && pg_strcasecmp(fc_typename, "none") == 0)
		{
			/* NONE的特殊情况 */
			fc_typeid = InvalidOid;
			fc_typmod = -1;
		}
		else
		{
			/* 使用完整的解析器解析类型名称 */
			parseTypeString(fc_typename, &fc_typeid, &fc_typmod, false);
		}
		if (*fc_nargs >= FUNC_MAX_ARGS)
			ereport(ERROR,
					(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
					 errmsg("too many arguments")));

		fc_argtypes[*fc_nargs] = fc_typeid;
		(*fc_nargs)++;
	}

	pfree(fc_rawname);
}
